﻿using System;
using System.Collections.Generic;
using System.Reflection;
using MugenInjection.Interface;

namespace MugenInjection.Infrastructure
{
    /// <summary>
    /// Select constructor for specified service.
    /// </summary>
    public sealed class DefaultConstructorResolver : IConstructorResolver
    {
        #region Fields

        private List<ConstructorInfo> _cachedConstructors;
        private Type _serviceType;
        private ConstructorInfo _constructorInfo;

        #endregion

        #region Properties

        internal ConstructorInfo ConstructorInfo
        {
            get { return _constructorInfo; }
        }

        #endregion

        #region Implementation of IConstructorResolver

        /// <summary>
        /// Selects constructor for the specified service type.
        /// </summary>
        /// <param name="service">The specified service <see cref="Type"/>.</param>
        /// <param name="bindingContext">The specified <see cref="IBindingContext"/>s.</param>
        /// <returns>An instance of the <see cref="System.Reflection.ConstructorInfo"/>.</returns>
        public ConstructorInfo Resolve(Type service, IBindingContext bindingContext)
        {
            if (_constructorInfo != null && _constructorInfo.DeclaringType == service)
                return _constructorInfo;
            return FindConstructor(service, bindingContext.Injector.Settings, bindingContext.Parameters);
        }

        /// <summary>
        /// Sets the <see cref="System.Reflection.ConstructorInfo"/> for the current <see cref="IConstructorResolver"/>.
        /// </summary>
        /// <param name="constructorInfo">The specified <see cref="System.Reflection.ConstructorInfo"/></param>
        public void SetConstructor(ConstructorInfo constructorInfo)
        {
            Validate.ArgumentNotNull(constructorInfo, "constructorInfo");
            _constructorInfo = constructorInfo;
        }

        #endregion

        #region Method

        private List<ConstructorInfo> GetConstuctors(Type service, IInjectorSettings settings)
        {
            if (_cachedConstructors != null && _cachedConstructors.Count != 0 && _serviceType == service)
                return _cachedConstructors;

            _serviceType = service;
            _cachedConstructors = new List<ConstructorInfo>();
#if NETFX_CORE
            var constructorInfos =  new List<ConstructorInfo>(service.GetTypeInfo().DeclaredConstructors);
#else
            var constructorInfos = new List<ConstructorInfo>(service.GetConstructors());
#endif
            for (int index = 0; index < constructorInfos.Count; index++)
            {
                ConstructorInfo constructorInfo = constructorInfos[index];
                if (constructorInfo.IsStatic)
                {
                    constructorInfos.Remove(constructorInfo);
                    index--;
                    continue;
                }
                if (!constructorInfo.IsDefined(settings.AttributeForInject, true)) continue;
                _cachedConstructors.Add(constructorInfo);
            }
            if (_cachedConstructors.Count == 0)
                _cachedConstructors = constructorInfos;
            if (_cachedConstructors.Count == 1)
                _constructorInfo = _cachedConstructors[0];
            return _cachedConstructors;
        }

        private ConstructorInfo FindConstructor(Type service, IInjectorSettings settings, IList<IInjectionParameter> parameters)
        {
            ConstructorInfo result = null;
            int bestCount = -1;
            int currentCountParameter = 0;
            var constructorInfos = GetConstuctors(service, settings);
            if (constructorInfos.Count == 1)
                return constructorInfos[0];
            foreach (ConstructorInfo constructorInfo in constructorInfos)
            {
                int currentCount = 0;
                var parameterInfos = InjectorUtilsInternal.GetParameterInfo(constructorInfo);
                for (int i = 0; i < parameterInfos.Length; i++)
                {
                    ParameterInfo parameterInfo = parameterInfos[i];
                    for (int index = 0; index < parameters.Count; index++)
                    {
                        IInjectionParameter injectionParameter = parameters[index];
                        if (injectionParameter.CanResolve(constructorInfo, parameterInfo))
                        {
                            currentCount++;
                            break;
                        }
                    }
                }
                if (bestCount > currentCount) continue;
                if (bestCount == currentCount && parameterInfos.Length > currentCountParameter) continue;
                currentCountParameter = parameterInfos.Length;
                result = constructorInfo;
                bestCount = currentCount;
            }
            return result;
        }

        #endregion
    }
}